package productidentification;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import common.Result;
import common.StringOps;

/**
 * @author Paula Chen (Javadoc), Ryan Cheatham (implementation)
 * 
 *         Factory of product identifier, build the ProductIdentifier.
 * 
 */
public enum ProductIdentifierFactory
{
  INSTANCE;

  private ProductIdentifierRegistry registry;

  /**
   * Constructs a new product identifier factory.
   */
  private ProductIdentifierFactory()
  {
    registry = new ProductIdentifierRegistry();
  }

  /**
   * Gets a product identifier for the use of identifying a product by its barcode.
   * 
   * @return A product identifier if a Product Identifier plugin has been registered successfully,
   *         otherwise this method returns <tt>null</tt>.
   * 
   * @see IProductIdentifier
   */
  public static IProductIdentifier getProductIdentifier()
  {
    return INSTANCE._getProductIdentifier();
  }

  /**
   * add a product identifier
   * 
   * @param className
   *          the class name of the product identifier to add
   * @param name
   *          the name of the product identifier
   * @param description
   *          description of the product
   * @return result indicating if the operation is done successfully
   */
  public static Result addProductIdentifier(String className, String name, String description)
  {
    return INSTANCE._addProductIdentifier(className, name, description);
  }

  /**
   * Get the registered identifier class names
   * 
   * @return an iterator through the class names of the registered identifiers
   */
  public static Iterator<String> getRegisteredIdentifierClassNames()
  {
    return INSTANCE._getRegisteredIdentifierClassNames();
  }

  /**
   * Gets the registered identifier names.
   * 
   * @return an iterator through the names of the registered identifiers.
   */
  public static Iterator<String> getRegisteredIdentifierNames()
  {
    return INSTANCE._getRegisteredIdentifierNames();
  }

  /**
   * Remove a product identifier
   * 
   * @param className
   *          the class name of the product identifier to remove
   * @return a result indicating if the operation is done successfully
   */
  public static Result removeProductIdentifier(String className)
  {
    return INSTANCE._removeProductIdentifier(className);
  }

  /**
   * @see #getProductIdentifier()
   */
  private IProductIdentifier _getProductIdentifier()
  {
    Iterator<ProductIDRegistryEntry> plugins = registry.getEntries();
    IProductIdentifierLink identifier = null;
    while (plugins.hasNext())
    {
      ProductIDRegistryEntry entry = plugins.next();
      IProductIdentifier plugin = loadPlugin(entry);
      if (plugin != null)
      {
        IProductIdentifierLink next = buildProductIdentifierLink(plugin);
        next.setNextLink(identifier);
        identifier = next;
      }
    }

    return identifier;
  }

  /**
   * Attempts to load a Product Identifier plugin from the given registry entry. If the supplied
   * {@link IProductIdentifierLink} is not <tt>null</tt>, the new identifier will be linked to the
   * supplied one. When the new identifier runs, if it fails it will call the supplied identifier
   * so it can attempt to identify a product. It then blindly returns the result of the supplied
   * identifier.
   * 
   * @param entry
   *          The registry entry that contains the class name for the plugin.
   * @param identifier
   *          The current product identifier in the linked list.
   * @return A new Product Identifier
   * 
   * @author Ryan Cheatham
   */
  private IProductIdentifier loadPlugin(ProductIDRegistryEntry entry)
  {
    try
    {
      Class<?> idClass = Class.forName(entry.getClassName());
      Object idObject = idClass.newInstance();
      if (idObject instanceof IProductIdentifier)
        return (IProductIdentifier) idObject;
    }
    catch (ClassNotFoundException e)
    {
      System.err.print("WARNING: Product Identifier plugin could not be found! ");
      System.err.println(e.getMessage());
      e.printStackTrace();
    }
    catch (InstantiationException e)
    {
      System.err.print("WARNING: Product Identifier plugin could not be instantiated! ");
      System.err.println(e.getMessage());
      e.printStackTrace();
    }
    catch (IllegalAccessException e)
    {
      System.err.print("WARNING: Product Identifier plugin could not be accessed! ");
      System.err.println(e.getMessage());
      e.printStackTrace();
    }

    return null;
  }

  /**
   * Builds a Product Identifier link wrapped around the given Product Identifier.
   * 
   * @param identifier
   *          a Product Identifier
   * @return an IProductIdentifierLink object which decorates the given identifier with chain of
   *         responsibility features.
   * 
   * @author Ryan Cheatham
   */
  private IProductIdentifierLink buildProductIdentifierLink(final IProductIdentifier identifier)
  {
    return new IProductIdentifierLink()
    {
      IProductIdentifierLink next = null;

      @Override
      public String identifyProduct(String barcode) throws Exception
      {
        return tryIdentifyProduct(barcode);
      }

      @Override
      public String getName()
      {
        return identifier.getName();
      }

      @Override
      public String getDescription()
      {
        return identifier.getDescription();
      }

      @Override
      public String tryIdentifyProduct(String barcode)
      {
        String description;
        try
        {
          description = identifier.identifyProduct(barcode);
        }
        catch (Exception e)
        {
          description = null;
        }

        if (StringOps.isNullOrEmpty(description))
          description = passToNextLink(barcode);

        return description;
      }

      @Override
      public String passToNextLink(String barcode)
      {
        if (next == null)
          return null;
        else
          return next.tryIdentifyProduct(barcode);
      }

      @Override
      public void setNextLink(IProductIdentifierLink link)
      {
        next = link;
      }
    };
  }

  /**
   * @see #addProductIdentifier(String, String, String)
   */
  private Result _addProductIdentifier(String className, String name, String description)
  {
    ProductIDRegistryEntry entry = new ProductIDRegistryEntry(className, name, description);
    return registry.register(entry);
  }

  /**
   * @see #removeProductIdentifier(String)
   */
  private Result _removeProductIdentifier(String className)
  {
    ProductIDRegistryEntry entry = new ProductIDRegistryEntry(className);
    return registry.unRegister(entry);
  }

  /**
   * @see #getRegisteredIdentifierClassNames()
   */
  private Iterator<String> _getRegisteredIdentifierClassNames()
  {
    List<String> classNames = new ArrayList<String>();

    Iterator<ProductIDRegistryEntry> itr = registry.getEntries();
    while (itr.hasNext())
      classNames.add(itr.next().getClassName());

    return Collections.unmodifiableList(classNames).iterator();
  }

  private Iterator<String> _getRegisteredIdentifierNames()
  {
    List<String> names = new ArrayList<String>();

    Iterator<ProductIDRegistryEntry> itr = registry.getEntries();
    while (itr.hasNext())
      names.add(itr.next().getName());

    return Collections.unmodifiableList(names).iterator();
  }
}
